﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SchemaGraph.Interfaces;
using SchemaGraph.Interfaces.Edges;
using SchemaGraph.Interfaces.Vertexes;
using System.Xml.Schema;

namespace SchemaGraph.Generator
{
    /// <summary>
    /// Takes a schema, and dumps it into a schema graph.
    /// This implements a two pass system.  First pass simply defines
    /// the datatypes.  Second pass parses properties and creates edges
    /// between the datatypes.  Thus you could name the passes:
    /// 'Create Vertexes' and 'Create Edges'
    /// </summary>
    public class Generator
    {
        IGraph m_graph;
        /// <summary>
        /// Maps from whatever xml schema object prompted the creation of the
        /// generator context to the generator context so on the second pass I
        /// can find things.
        /// 
        /// </summary>
        Dictionary<object, GeneratorContext> m_contexts = new Dictionary<object, GeneratorContext>();
        GeneratorContext m_rootContext = new GeneratorContext(null, null);
        GeneratorContext m_currentContext;

        public Generator(IGraph graph, XmlSchema schema) 
        { 
            m_graph = graph;
            m_currentContext = m_rootContext;
            //Create the base schema simple types.
            //http://www.w3.org/TR/xmlschema-2/#built-in-datatypes
            ISimpleTypeVertex anyType = CreateSimple("anyType");
            ISimpleTypeVertex anySimpleType = CreateSimple("anySimpleType");
            m_graph.Edges.Restrict(anyType, anySimpleType);
            CreateSimpleTypes(anySimpleType, new string[]
            {
                "duration", "dateTime", "time", "date", "gYearMonth"
                , "gYear", "gMonth", "gDay", "gMonthDay", "string"
                , "boolean", "base64Binary", "hexBinary", "float"
                , "double", "decimal", "anyURI", "QName", "NOTATION"
            });
            CreateSimpleTypes("string", new string[] { "normalizedString" });
            CreateSimpleTypes("normalizedString", new string[] { "token" });
            CreateSimpleTypes("token", new string[] { "language", "Name", "NMTOKEN" });
            CreateSimpleTypes("Name", new string[] { "NCName" });
            CreateSimpleTypes("NCName", new string[] { "ID", "IDREF", "ENTITY" });

            CreateSimpleTypes("decimal", new string[] { "integer" });
            CreateSimpleTypes("integer", new string[] { "nonPositiveInteger", "long", "nonNegativeInteger" });
            CreateSimpleTypes("nonPositiveInteger", new string[] { "negativeInteger" });
            CreateSimpleTypes("long", new string[] { "int" });
            CreateSimpleTypes("nonNegativeInteger", new string[] { "unsignedLong", "positiveInteger" });
            CreateSimpleTypes("int", new string[] { "short" });
            CreateSimpleTypes("short", new string[] { "byte" });
            CreateSimpleTypes("unsignedLong", new string[] { "unsignedInt" });
            CreateSimpleTypes("unsignedInt", new string[] { "unsignedShort" });
            CreateSimpleTypes("unsignedShort", new string[] { "unsignedByte" });

            //OK, there are the simple types done, save for lists.
            CreateSimpleList("NMTOKEN", "NMTOKENS");
            CreateSimpleList("IDREF", "IDREFS");
            CreateSimpleList("ENTITY", "ENTITIES");
        }

        private void CreateSimpleTypes(string baseClass, string[] types)
        {
            CreateSimpleTypes(m_currentContext.Lookup(baseClass), types);
        }

        //Create a list of derived types by restriction
        private void CreateSimpleTypes(IDatatypeVertex anySimple, string[] types)
        {
            if (anySimple == null)
                throw new InvalidOperationException();
            foreach (string type in types)
            {
                ISimpleTypeVertex vertex = CreateSimple(type);
                m_graph.Edges.Restrict(anySimple, vertex);
            }
        }

        private ISimpleTypeVertex CreateSimple(string name)
        {
            ISimpleTypeVertex retval = m_graph.Vertexes.CreateSimpleTypeVertex(name);
            m_currentContext.AddDatatype(name, retval);
            return retval;
        }

        private ISimpleTypeVertex CreateSimpleList(string parent, string name)
        {
            ISimpleTypeVertex retval = CreateSimple(name);
            m_graph.Edges.DeriveList((ISimpleTypeVertex)m_currentContext.Lookup(parent), retval);
            return retval;
        }

        private void SetCurrentContext(GeneratorContext context)
        {
            m_currentContext = context;
        }
    }
}
